﻿/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "playerwidget.h"
#if defined(Q_OS_WIN) && !defined(Q_CC_GNU)
#include <mmdeviceapi.h>
#endif
#include <QKeyEvent>
#include <QMouseEvent>
#include <QDebug>
#include <QPainter>

/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void quit(int rc)
{
    SDL_Quit();
    exit(rc);
}


PlayerWidget::PlayerWidget(QWidget *parent)
    : QVideoWidget(parent),_audio_buffer(1024*1024)
{
    setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

    QPalette p = palette();
    p.setColor(QPalette::Window, Qt::black);
    setPalette(p);

    setAttribute(Qt::WA_OpaquePaintEvent);


    Time=0;
    RendererRef = nullptr;
    WindowRef = nullptr;
    textureRef =  nullptr;
    position = 0;
    memset(&_audiospec,0,sizeof(_audiospec));

    _frame_width=0;
    _frame_height=0;




    /*
    In order to do rendering, I need to save the window and renderer contexts
    of this window.
    I use SDL_CreateWindowFrom and pass it the winId() of the widget I wish to
    render to. In this case, I want to render to the main central widget.
    */
    WId id=this->winId();
    SDL_Window * ref=SDL_CreateWindowFrom((void *)id);
    SetWindow(ref);
    SetRenderer(SDL_CreateRenderer(GetWindow(), -1, 0));

    connect(this,SIGNAL(Sig_NewFrame(int ,int )),this,SLOT(Slot_Render(int ,int )));
    connect(this,SIGNAL(Sig_FrameSizeChanged(int ,int )),this,SLOT(Slot_SDLInit(int ,int)));

    _framecount=RateCountInit(15,20);
//    _byteratevideocount=RateCountInit(15,20);
//    _byterateaudiocount=RateCountInit(15,20);

    if(Time==0)
    {

        QTimer *timer = new QTimer(this);
        connect(timer, SIGNAL(timeout()), this, SLOT(updateRateCount()));
        timer->start(500);
    }


}

/* Prepare a dummy image. */
static void FillYuvImage(Uint8* pYuv, int nWidth, int nHeight, int nIndex)
{
#if 0
    int x, y, i;

    i = nIndex;

    BYTE* pY = pYuv;
    BYTE* pU = pYuv + nWidth * nHeight;
    BYTE* pV = pYuv + nWidth * nHeight * 5 / 4;

    /* Y */
    for (y = 0; y < nHeight; y++)
    {
        for (x = 0; x < nWidth; x++)
        {
            pY[y * nWidth + x] = x + y + i * 3;
        }
    }

    /* Cb and Cr */
    for (y = 0; y < nHeight / 2; y++)
    {
        for (x = 0; x < nWidth / 2; x++)
        {
            pU[y * (nWidth / 2) + x] = 128 + y + i * 2;
            pV[y * (nWidth / 2) + x] = 64 + x + i * 5;
        }
    }
#else
    Uint8* pY = pYuv;
    Uint8* pU = pYuv + nWidth * nHeight;
    Uint8* pV = pYuv + nWidth * nHeight * 5 / 4;

    memset(pY,rand()%255, nWidth * nHeight);
    memset(pU, rand()%255, nWidth * nHeight/4);
    memset(pV, rand()%255, nWidth * nHeight / 4);
#endif
}
void  PlayerWidget::AddInfoMap(const QString  & key,const QString & value)
{

    _infomap.insert(key,value);

    QList<QString> list=_infomap.values();

    QList<QString>::const_iterator iter = list.cbegin();

    _infostr.clear();
   while (iter != list.cend())
   {

         _infostr+=*iter;
         _infostr+=" ";
         iter++;
   }

//   qDebug()<<"_infostr:"<<_infostr;
}
void PlayerWidget::CheckNeetReSDLInit(int w,int h)
{


    if(_frame_width<=0||_frame_height<=0)
    {
        qDebug()<<"neet to reinit sdl window,w="<<w<<",h="<<h<<__LINE__;
        _frame_width=w;
        _frame_height=h;
//        emit Sig_FrameSizeChanged(w,h);
        Slot_SDLInit(w,h);
    }else if(_frame_width!=w||_frame_height!=h)
    {
        qDebug()<<"neet to reinit sdl window,w="<<w<<",h="<<h<<__LINE__;
        _frame_width=w;
        _frame_height=h;
//        emit Sig_FrameSizeChanged(w,h);
        Slot_SDLInit(w,h);
    }

}
void PlayerWidget::OnGotVideoFrame(const char *y, size_t y_size,
                             const char *u, size_t u_size,
                             const char *v, size_t v_size,
                             uint64_t timestamp,uint32_t w,uint32_t h)
{



    SampleInsert(_framecount,1);
//    qDebug()<<QString::number(GetTmpRate(_framecount), 'f', 2);
//    SampleInsert(_byteratevideocount,y_size+u_size+v_size);
    _frameBufLock.lock();

    Uint8* pY = _frameBuffer;
    Uint8* pU = _frameBuffer + w * h;
    Uint8* pV = _frameBuffer + w * h * 5 / 4;
    memcpy(pY,y,w*h);
    memcpy(pU,u,w*h/4);
    memcpy(pV,v,w*h/4);


    _stride[0]=y_size;
    _stride[1]=u_size;
    _stride[2]=v_size;

    _frameBufLock.unlock();

//    FillYuvImage(_frameBuffer, _frame_width, _frame_height, 0);

//    qDebug()<<"file:"<<__FILE__<<",line:"<<__LINE__<<",func:"<<__FUNCTION__;
//    AddInfoMap(info_resolution,QString("%1 X %2").arg(640).arg(480));
    emit Sig_NewFrame(w,h);
}

void PlayerWidget::OnGotAudioFrame(const char *pcm,
                     uint32_t bitsPerSample,
                     uint32_t sampleRate,
                     uint32_t numberOfChannels,
                     int64_t numberofFrames,
                     int64_t timestamp)
{

//       SampleInsert(_byterateaudiocount,numberOfChannels*numberofFrames*bitsPerSample/8);

       PushinAudioData(pcm,bitsPerSample,sampleRate,numberOfChannels,numberofFrames,timestamp);
}

void PlayerWidget::Reset()
{
    close_audio(_audiodevice);
    if(textureRef!=nullptr)
    {
        SDL_DestroyTexture(textureRef);
    }

    _infomap.clear();

    _audio_buffer.cleanup();


    textureRef=0;
    _frame_width=0;
    _frame_height=0;

    RateCountReset(_framecount);
//    RateCountReset(_byteratevideocount);
//    RateCountReset(_byterateaudiocount);
}
PlayerWidget::~PlayerWidget()
{
    Reset();

    if(RendererRef!=nullptr)
    {
        SDL_DestroyRenderer(RendererRef);
    }
    if(WindowRef!=nullptr)
    {
        SDL_DestroyWindow(WindowRef);
    }

    RendererRef = 0;
    WindowRef = 0;

    if(_framecount)
    {
        RateCountDestroy(_framecount);
        _framecount=0;
    }

//    if(_byteratevideocount)
//    {
//        RateCountDestroy(_byteratevideocount);
//        _byteratevideocount=0;
//    }
//    if(_byterateaudiocount)
//    {
//        RateCountDestroy(_byterateaudiocount);
//        _byterateaudiocount=0;
//    }

}
void PlayerWidget::Slot_SDLInit( int w,int h)
{
    if(!RendererRef)
        return;
    AddInfoMap(info_resolution,QString("%1X%2").arg(w).arg(h));
    if(textureRef!=nullptr)
    {
        SDL_DestroyTexture(textureRef);
    }
    textureRef = SDL_CreateTexture(RendererRef, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, w, h);
}
void PlayerWidget::Slot_Render(int w,int h)
{


    CheckNeetReSDLInit(w,h);
    if(!RendererRef)
        return;
    //    FillYuvImage(_frameBuffer, W, H, index++);
    _frameBufLock.lock();
    Uint8* pY = _frameBuffer;
    Uint8* pU = _frameBuffer + _frame_width * _frame_height;
    Uint8* pV = _frameBuffer + _frame_width * _frame_height * 5 / 4;
    int ret_value=0;

    if(textureRef!=NULL)
    {
        ret_value=SDL_RenderClear(RendererRef);
        if(ret_value)
        {
            qDebug()<<"render clear failed"<<SDL_GetError();
        }

        ret_value=SDL_UpdateYUVTexture(textureRef, NULL,
                    pY, _frame_width,
                    pU, _frame_width / 2,
                    pV, _frame_width / 2);
        if(ret_value)
        {
            qDebug()<<"update YUV Texture failed"<<SDL_GetError();
        }

        ret_value=SDL_RenderCopy(RendererRef, textureRef, NULL, NULL);
        if(ret_value)
        {
            qDebug()<<"render copy failed"<<SDL_GetError();
        }
        ret_value=SDLTest_DrawString(RendererRef, 4, 4, _infostr.toLatin1().data());
        if(ret_value)
        {
            qDebug()<<"draw string failed"<<SDL_GetError();
        }
        SDL_RenderPresent(RendererRef);

    }
    _frameBufLock.unlock();
}

void PlayerWidget::updateRateCount()
{
    AddInfoMap(info_framerate,QString("%1").arg(QString::number(GetTmpRate(_framecount), 'f', 2)));

//    AddInfoMap(info_byterate,QString("%1-%2").arg(QString::number(GetTmpRate(_byteratevideocount)/1024, 'f', 2)).arg(QString::number(GetTmpRate(_byterateaudiocount)/1024, 'f', 3)));
}
void PlayerWidget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape && isFullScreen()) {
        setFullScreen(false);
        event->accept();
    } else if (event->key() == Qt::Key_Enter && event->modifiers() & Qt::Key_Alt) {
        setFullScreen(!isFullScreen());
        event->accept();
    } else {
        QVideoWidget::keyPressEvent(event);
    }
}

void PlayerWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
    setFullScreen(!isFullScreen());
    event->accept();
}

void PlayerWidget::mousePressEvent(QMouseEvent *event)
{
    QVideoWidget::mousePressEvent(event);
}
void PlayerWidget::SetWindow(SDL_Window * ref) {
    WindowRef = ref;
}

void PlayerWidget::SetRenderer(SDL_Renderer * ref) {
    RendererRef = ref;
    SDL_SetRenderDrawColor(RendererRef, 0xFF, 0x0, 0xFF, 0xFF);
}

SDL_Window * PlayerWidget::GetWindow() {
    return WindowRef;
}

SDL_Renderer * PlayerWidget::GetRenderer() {
    return RendererRef;
}

void    PlayerWidget::close_audio(SDL_AudioDeviceID & audiodevice)
{
    if (audiodevice != 0) {
        SDL_CloseAudioDevice(audiodevice);
        audiodevice = 0;
    }
}

void    PlayerWidget::open_audio(SDL_AudioDeviceID & audiodevice,SDL_AudioSpec & spec)
{
    /* Initialize fillerup() variables */
    audiodevice = SDL_OpenAudioDevice(NULL, SDL_FALSE, &spec, NULL, 1);
    if (!audiodevice) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError());
        ::quit(-1);
    }
    /* Let the audio run */
    SDL_PauseAudioDevice(audiodevice, SDL_FALSE);
}

void PlayerWidget::reopen_audio(SDL_AudioDeviceID & audiodevice,SDL_AudioSpec & spec)
{
    close_audio(audiodevice);
    open_audio(audiodevice,spec);
}

int PlayerWidget::ExtractAudioData(Uint8 * stream, int len)
{
    return _audio_buffer.read(stream, len);
}

int PlayerWidget::PushinAudioData(const char *pcm, uint32_t bitsPerSample, uint32_t sampleRate, uint32_t numberOfChannels, int64_t numberofFrames, int64_t timestamp)
{

    CheckNeetReAudioInit(_audiospec,sampleRate,bitsPerSample,numberOfChannels);
    return _audio_buffer.write(pcm, numberOfChannels*numberofFrames*bitsPerSample/8);
}
void  PlayerWidget::fillaudioup(void *user_data, Uint8 * stream, int len)
{
    PlayerWidget * player=(PlayerWidget*) user_data;
    player->ExtractAudioData(stream,len);
}
void    PlayerWidget::CheckNeetReAudioInit(SDL_AudioSpec & spec,int sampleRate,int bytesPerSample,int channels)
{
    if(_audiospec.freq==0||_audiospec.format==0||_audiospec.channels==0)
    {
        spec.freq = sampleRate;
        spec.format = AUDIO_S16LSB;
        spec.channels = channels;
        spec.silence = 0;
        spec.samples = 480;
        spec.callback = fillaudioup;
        spec.userdata=this;
        open_audio(_audiodevice,spec);
    }else if(spec.freq!=sampleRate||spec.channels!=channels){
        spec.freq = sampleRate;
        spec.format = AUDIO_S16LSB;
        spec.channels = channels;
        spec.silence = 0;
        spec.samples = 480;
        spec.callback = fillaudioup;
        reopen_audio(_audiodevice,spec);
    }

}
